#include "WrapCodeGenerator.h"
#include "DefineCommon.h"
// #include <QtCore/QFile>
#include <iostream>
#include <fstream>


WrapCodeGenerator::WrapCodeGenerator()
{
}

WrapCodeGenerator::~WrapCodeGenerator()
{
}

void WrapCodeGenerator::collectionToFiles(CppInfo::CppCollectionPtr collPtr, const EString& dirName)
{
    /**generate wrap class defines*/
    EList<CppInfo::ClassPtr> wrapCList;
    
    for(EMap<EString, CppInfo::ClassPtr>::iterator it = collPtr->mapClassPtr.begin();
            it != collPtr->mapClassPtr.end();
            it++)
    {
        // if(collPtr->needClassNameList.contains(it.key()) == false)
        if(EString::EStringListContains(collPtr->needClassNameList, it->first))
        {
            continue;
        }

        CppInfo::ClassPtr c = it->second;

        //newC is a copy of c, because in wrap code generation, some information may be changed.
        CppInfo::ClassPtr newC(new CppInfo::Class);
        newC->copyFrom(c);

        EStringList parentNameList;
        for(int i =0 ; i < c->parentNameList.size(); i++)
        {
            // if(collPtr->mapClassPtr.contains(c->parentNameList[i]))
            if(collPtr->mapClassPtr.find(c->parentNameList[i]) != collPtr->mapClassPtr.end())
            {
                parentNameList.push_back(c->parentNameList[i]);
                //copy parent's vars and funs to this class
                CppInfo::ClassPtr parent = collPtr->mapClassPtr[c->parentNameList[i]];
                this->copyClass1ToClass2(parent, newC);
            }
        }
        newC->parentNameList = parentNameList;
        wrapCList.push_back(this->genWrapClass(newC));
    }

    /**generate class's .h .cpp files*/
    for(int i = 0; i < wrapCList.size(); i++)
    {
        /**if orig class is a pure virtual class, then do not generate code file*/
        if(wrapCList[i]->userData["constructType"] == "nosimpleConstruct")
        {
            continue;
        }
        EString fileName = wrapCList[i]->name;
        fileName += ".h";
        this->classToFile_h(wrapCList[i], dirName+"/"+fileName);

        fileName = wrapCList[i]->name;
        fileName += ".cpp";
        this->classToFile_cpp(wrapCList[i], dirName+"/"+fileName);
    }

    m_wrapClassList = wrapCList;
}

CppInfo::ClassPtr WrapCodeGenerator::genWrapClass(CppInfo::ClassPtr c)
{
    CppInfo::ClassPtr newC(new CppInfo::Class);
    newC->name = this->toWrapClassName(c->name);
    newC->userData["origClassName"]=c->name;

    EMap<EString, CppInfo::FunctionPtr> getFuns;
    EMap<EString, CppInfo::FunctionPtr> setFuns;

    for(int i =0; i < c->functionList.size(); i++ )
    {
        CppInfo::FunctionPtr fun = c->functionList[i];
        EString returnType = this->unifyVariableType(fun->returnType);
        int paramListSize = fun->parameterList.size();
        EString name = fun->name;
        EString namePrefix = name.left(3);
        EString nameSuffix = name.right(name.size() - 3);
        EList<CppInfo::VariablePtr> vars = fun->parameterList;
        
        /**pick out get functions, if picked, add a VariablePtr to newC*/
        if(
                namePrefix == "get" 
                && paramListSize == 0 
                && this->ifSupportVariableType( returnType ) 
                && getFuns.find(nameSuffix) != getFuns.end()
                // && getFuns.contains(nameSuffix) == false
                )
        {
            getFuns[nameSuffix] = fun;
            CppInfo::VariablePtr newVar(new CppInfo::Variable);
            newVar->typeName = returnType;
            newVar->name = nameSuffix;
            newVar->userData = "get";
            newC->variableList.push_back(newVar);
        }
        /**pick out set functions*/
        else if(namePrefix == "set" 
                && returnType == "void" 
                && paramListSize == 1 
                && this->ifSupportVariableType(vars[0]->typeName) 
                && setFuns.find(nameSuffix) != getFuns.end()
                // && setFuns.contains(nameSuffix) == false
                )
        {
            setFuns[nameSuffix] = fun;
        }
    }

    /**Iterator setFuns to find if a newC's VariablePtr has both setFun and getFun*/
    for(EMap<EString, CppInfo::FunctionPtr>::iterator it = setFuns.begin();
            it != setFuns.end();
            it++)
    {
        EString nameSuffix = it->first;
        for(int i = 0 ; i < newC->variableList.size(); i++)
        {
            if(newC->variableList[i]->name == nameSuffix)
            {
                newC->variableList[i]->userData = "get set";
            }
        }
    }

    /**charge if construct is enable*/
    bool ifSimpleConstruct = false;
    for(int i = 0 ; i < c->constructorList.size(); i++)
    {
        if(c->constructorList[i]->parameterList.size() == 0)
        {
            ifSimpleConstruct = true;
            break;
        }
    }

    if(ifSimpleConstruct == true && c->ifPureVirtual == false)
    {
        newC->userData["constructType"] = "simpleConstruct";
    }
    else
    {
        newC->userData["constructType"] = "nosimpleConstruct";
    }

    return newC;
}

void WrapCodeGenerator::classToFile_h(CppInfo::ClassPtr classPtr, const EString& fileName)
{
    std::ofstream file(fileName.data());
    if(!file)
    {
        ELOG_ERROR("Failed to open file %s", fileName.data());
        return;
    }

    {
        EString content;
        this->genHeader_h(classPtr, content);
        this->genInclude_h(classPtr, content);
        this->genClassDecl_h(classPtr, content);
        this->genClassVariablesOrignalPtr_h(classPtr, content);
        this->genClassFunSetOrigPtr_h(classPtr, content);
        this->genClassFunGetOrigPtr_h(classPtr, content);
        this->genClassFunFillPropertys_h(classPtr, content);
        this->genClassFunToProperty_h(classPtr, content);
        this->genClassFunToOrig_h(classPtr, content);
        this->genClassConstructor_h(classPtr, content);
        this->genClassDestructor_h(classPtr, content);
        this->genClassConstantCode_h(content);
        this->genEnd_h(classPtr, content);
        // this->genClassFunctions(classPtr, content);
        // this->genClassFunGetOrigPtr(classPtr, content);
        // this->genClassFunNewOrigPtr(classPtr, content);

        file<<content.data()<<std::endl;
    }
    file.close();
}

void WrapCodeGenerator::classToFile_cpp(CppInfo::ClassPtr classPtr, const EString& fileName)
{
    std::ofstream file(fileName.data());
    if(!file)
    {
        ELOG_ERROR("Failed to open file %s", fileName.data());
        return;
    }


    {
        EString content;
        this->genInclude_cpp(classPtr, content);

        file<<content.data()<<std::endl;
    }
    file.close();
}

void WrapCodeGenerator::genHeader_h(CppInfo::ClassPtr classPtr, EString& content)
{
    EString name = classPtr->name;
    content += EString("#ifndef ") + name + "_H" + "\n";
    content += EString("#define ") + name + "_H" + "\n";
    content += "\n";

}

void WrapCodeGenerator::genInclude_h(CppInfo::ClassPtr classPtr, EString& content)
{
    
    content += EString("#include <DefineCommon.h>\n");
    content += EString("#include <ItemBase.h>\n");
    // content += EString("#include <ItemWrapOsgFunction.h>\n");

    //add parent class header include
    // for(int i = 0 ; i < classPtr->parentNameList.size(); i++)
    // {
        // EString parentName = this->toWrapClassName(classPtr->parentNameList[i]);
        // content += EString("#include <") + parentName + ".h>\n";
    // }

    EString name = classPtr->userData["origClassName"];
    name.replace("::", "/");
    content += EString("#include <") + name + ">\n";
    content += "\n";
}


void WrapCodeGenerator::genClassDecl_h(CppInfo::ClassPtr classPtr, EString& content)
{
    EString name = classPtr->name;

    content += EString("class ") + name + ";\n";
    content += EString("typedef SmartPtr(") + name + ") " + name + "Ptr;\n";
    content += EString("class ") + name + ": public ItemBase\n";

    content += EString("{") + "\n";
    content += EString("public:") + "\n";
}



void WrapCodeGenerator::genClassVariablesOrignalPtr_h(CppInfo::ClassPtr classPtr, EString& content)
{
    EString name = classPtr->userData["origClassName"];
    content += EString("    ")+name+"* m_origPtr;\n";
    // for(int i = 0 ; i < classPtr->variableList.size(); i++)
    // {
        // CppInfo::VariablePtr var = classPtr->variableList[i];
        // content += EString("    ") + var->typeName + " " + var->name+ ";\n";
    // }
    content += "\n";
}

void WrapCodeGenerator::genClassFunSetOrigPtr_h(CppInfo::ClassPtr classPtr, EString& content)
{
    EString name = classPtr->userData["origClassName"];
    content += EString("    virtual void setOrigPtr")+"(intptr_t origPtr)\n"; 
    content += EString("    {\n");
    content += EString("        this->m_origPtr = (")+name+"*)origPtr;\n";
    content += EString("    }\n");
}

void WrapCodeGenerator::genClassFunGetOrigPtr_h(CppInfo::ClassPtr classPtr, EString& content)
{
    EString name = classPtr->userData["origClassName"];
    content += EString("    virtual intptr_t getOrigPtr()\n");
    content += EString("    {\n");
    content += EString("        return (intptr_t)(this->m_origPtr);\n");
    content += EString("    }\n");
}

void WrapCodeGenerator::genClassFunFillPropertys_h(CppInfo::ClassPtr classPtr, EString& content)
{
    content += "    void fillPropertys()\n";
    content += "    {\n";
    content += "        this->clearAllPropertys();\n";
    
    for(int i = 0 ; i < classPtr->variableList.size(); i++)
    {
        CppInfo::VariablePtr var = classPtr->variableList[i];
        content += EString("        ItemPropertyPtr p")+EString::number(i)+" = NewSmartPtr(ItemProperty)();\n";
        content += EString("        p")+EString::number(i)+"->name= \"" + var->name + "\";\n";
        content += EString("        p")+EString::number(i)+"->typeName= \"" + var->typeName + "\";\n";
        content += EString("        p")+EString::number(i)+"->value= this->toEVariantValue(this->m_origPtr->get" +var->name + "());\n";
        if(var->userData.contains("set") == false)
        {
            content += EString("        p")+EString::number(i)+"->ifReadOnly=true;\n";
        }
        content += EString("        this->addProperty(p")+EString::number(i)+");\n";
        content += "\n";
    }
    content += "    }\n";
    content += "\n";

}

void WrapCodeGenerator::genClassFunToProperty_h(CppInfo::ClassPtr classPtr, EString& content)
{
    for(int i = 0 ; i < classPtr->variableList.size(); i++)
    {
        CppInfo::VariablePtr var = classPtr->variableList[i];
        content += EString("    void updateToProperty_") + var->name + "()\n";
        content += "    {\n";
        content += EString("        ItemPropertyPtr ibp = this->getProperty(\"")+var->name+"\");\n";
        content += "        if(ibp != nullptr)\n";
        content += "        {\n";
        content += EString("            ibp->value = this->toEVariantValue(this->m_origPtr->get")+var->name+"());\n";
        content += "        }\n";
        content += "    }\n";
        content += "\n";
    }

    content += "    virtual void updateToProperty(const EString& name)\n";
    content += "    {\n";
    content += "        if(name.isEmpty())\n";
    content += "        {\n";
    content += "        }\n";
    for(int i = 0 ; i < classPtr->variableList.size(); i++)
    {
        CppInfo::VariablePtr var = classPtr->variableList[i];
        content += EString("        else if(name == \"")+var->name+"\")\n";
        content += "        {\n";
        content += EString("        this->updateToProperty_")+var->name+"();\n";
        content += "        }\n";
    }
    content += "    }\n";
    content += "\n";
}

void WrapCodeGenerator::genClassFunToOrig_h(CppInfo::ClassPtr classPtr, EString& content)
{
    for(int i = 0 ; i < classPtr->variableList.size(); i++)
    {
        CppInfo::VariablePtr var = classPtr->variableList[i];
        if(var->userData.contains("set") == false)
        {
            continue;
        }

        EString newTypeName = var->typeName;
        newTypeName.replace("::", "_");
        newTypeName.replace(" ", "_");

        content += EString("    void updateToOrig_") + var->name + "()\n";
        content += "    {\n";
        content += EString("        ItemPropertyPtr ibp = this->getProperty(\"")+var->name+"\");\n";
        content += "        if(ibp != nullptr)\n";
        content += "        {\n";
        content += EString("            this->m_origPtr->set")+var->name 
            + "(this->to_"+newTypeName + "(ibp->value));\n";
        content += "        }\n";
        content += "    }\n";
        content += "\n";
    }

    content += "    virtual void updateToOrig(const EString& name)\n";
    content += "    {\n";
    content += "        if(name.isEmpty())\n";
    content += "        {\n";
    content += "        }\n";
    for(int i = 0 ; i < classPtr->variableList.size(); i++)
    {
        CppInfo::VariablePtr var = classPtr->variableList[i];
        if(var->userData.contains("set") == false)
        {
            continue;
        }
        content += EString("        else if(name == \"")+var->name+"\")\n";
        content += "        {\n";
        content += EString("        this->updateToOrig_")+var->name+"();\n";
        content += "        }\n";
    }
    content += "    }\n";
    content += "\n";
}

void WrapCodeGenerator::genClassConstructor_h(CppInfo::ClassPtr classPtr, EString& content)
{
    EString name = classPtr->name;
    EString origClassName = classPtr->userData["origClassName"];

    content += EString("    ") + name + "()\n";
    content += "    {\n";
    content += EString("        m_origPtr = new ")+origClassName+";\n";
    content += "        this->fillPropertys();\n";
    content += "    }\n";
    content += "\n";

}

void WrapCodeGenerator::genClassDestructor_h(CppInfo::ClassPtr classPtr, EString& content)
{
    EString name = classPtr->name;
    content += EString("    ~") + name + "()\n";
    content += EString("    {\n");
    content += EString("        delete m_origPtr");
    content += EString("    }\n");
    content += "\n";
}

void WrapCodeGenerator::genClassConstantCode_h(EString& content)
{
    content += "    /**functions do not need change*/\n";
    content += "    virtual EVariant getPropertyValue(const EString& name)\n";
    content += "    {\n";
    content += "        this->updateToProperty(name);\n";
    content += "        return ItemBase::getPropertyValue(name);\n";
    content += "    }\n";

    content += "\n";

    content += "    virtual void setPropertyValue(const EString& name, const EVariant& value)\n";
    content += "    {\n";
    content += "        ItemBase::setPropertyValue(name, value);\n";
    content += "        this->updateToOrig(name);\n";
    content += "    }\n";

}

void WrapCodeGenerator::genEnd_h(CppInfo::ClassPtr classPtr, EString& content)
{
    content += EString("};") + "\n";
    content += "#endif";
}

/*void WrapCodeGenerator::genClassFunctions(CppInfo::ClassPtr classPtr, EString& content)
{
    for(int i = 0 ; i < classPtr->variableList.size(); i++)
    {
        CppInfo::VariablePtr var = classPtr->variableList[i];
        EString ud = var->userData;
        if(ud.contains("get"))
        {
            content += EString("    ")+var->typeName+" get"+var->name+"()\n";
            content += EString("    {\n");
            content += EString("        return this->m_origPtr->get")+var->name+"();\n";
            content += EString("    }\n");
        }

        if(ud.contains("set"))
        {
            content += EString("    void set")+var->name+"("+var->typeName+" var)\n";
            content += EString("    {\n");
            content += EString("        this->m_origPtr->set")+var->name+"(var);\n";
            content += EString("    }\n");
        }
    }
}*/

/**void WrapCodeGenerator::genClassFunNewOrigPtr(CppInfo::ClassPtr classPtr, EString& content)
{
    bool ifSimpleConstruct = false;
    if(classPtr->userData["constructType"] == "simpleConstruct")
    {
        ifSimpleConstruct = true;
    }

    if(ifSimpleConstruct == true )
    {
        EString name = classPtr->userData["origClassName"];
        content += EString("    void newOrigPtr")+("()\n"); 
        content += EString("    {\n");
        content += EString("        this->m_origPtr")+(" = new ") + name + ";\n";
        content += EString("    }\n");
    }
}*/

/*void WrapCodeGenerator::genRttrEnable(CppInfo::ClassPtr classPtr, EString& content)
{
    content += EString("    RTTR_ENABLE(");
    for(int i = 0 ; i < classPtr->parentNameList.size(); i++)
    {
        if(i != classPtr->parentNameList.size() - 1)
        {
            content += this->toWrapClassName(classPtr->parentNameList[i]) + ",";
        }
        else
        {//remove the last ","
            content += this->toWrapClassName(classPtr->parentNameList[i]);
        }
    }
    content += EString(")\n");
}*/

    
void WrapCodeGenerator::genInclude_cpp(CppInfo::ClassPtr classPtr, EString& content)
{
    EString name = classPtr->name;
    content += EString("#include <autogen/") + name + ".h>\n";
    content += "\n";
}

/*void WrapCodeGenerator::genRttrReg(CppInfo::ClassPtr classPtr, EString& content)
{
    EString className = classPtr->name;
    content += EString("RTTR_PLUGIN_REGISTRATION\n");
    content += EString("{\n");
    content += EString("    rttr::registration::class_<")+className+">(\""+className+"\")\n";
    content += EString("    .constructor<>()\n");
    content += EString("    .method(\"")+"setOrigPtr"+"\", &"+className+"::setOrigPtr"+")\n";
    content += EString("    .method(\"")+"getOrigPtr"+"\", &"+className+"::getOrigPtr"+")\n";

    if(classPtr->userData["constructType"] == "simpleConstruct")
    {
        content += EString("    .method(\"")+"newOrigPtr"+"\", &"+className+"::newOrigPtr"+")\n";
    }

    content += EString("    .property(\"m_origPtr")+"\", &"+className+"::m_origPtr"+")\n";
    for(int i = 0 ; i < classPtr->variableList.size(); i++)
    {
        CppInfo::VariablePtr var = classPtr->variableList[i];
        if(var->userData.contains("set") == false)
        {
            content += EString("    .property_readonly(\"")+var->name+ "\", &"+className+"::get"+var->name+")\n";
        }
        else if(var->userData.contains("set") && var->userData.contains("get"))
        {
            content += EString("    .property(\"")+var->name+ "\""
                +", &"+className+"::get"+var->name
                +", &"+className+"::set"+var->name+")\n";
        }
    }

    content += EString("    ;\n");
    content += EString("}\n");
}*/

bool WrapCodeGenerator::ifSupportVariableType(const EString& varType)
{
    bool result = false;
    EStringList supportTypeList;
    supportTypeList.push_back("int");
    supportTypeList.push_back("unsigned int");
    supportTypeList.push_back("std::string");
    supportTypeList.push_back("const std::string &");
    supportTypeList.push_back("double");
    supportTypeList.push_back("bool");

    // if(supportTypeList.contains(varType))
    if(EString::EStringListContains(supportTypeList, varType)) 
    {
        result = true;
    }

    return result;
}
    
EString WrapCodeGenerator::unifyVariableType(const EString& varType)
{
    EString result = varType;
    if(varType == "const std::string &")
    {
        result = "std::string";
    }
    return result;
}

EString WrapCodeGenerator::toWrapClassName(EString name)
{
    name.replace("::", "_");
    name = EString("ItemWrap_")+name+"_gen";
    return name;

}
    
void WrapCodeGenerator::copyClass1ToClass2(CppInfo::ClassPtr c1, CppInfo::ClassPtr c2)
{
    for(int i = 0 ; i < c1->variableList.size(); i++)
    {
        c2->variableList.push_back(c1->variableList[i]);
    }

    for(int i = 0 ; i < c1->functionList.size(); i++)
    {
        c2->functionList.push_back(c1->functionList[i]);
    }
}
